#include <conio.h>
#include <types.h>
#include <string.h>
#include <list.h>
#include <queue.h>
#include <schedule.h>

#include <sys/kmalloc.h>
#include <sys/event.h>
#include <sys/task.h>
#include <sys/thread.h>
#include <sys/unistd.h>
#include <sys/sem.h>

/* todo: may be mem leaks in cleanup Sem->evt!? */
static sem_t *sem_list = NULL;	/* list of sems in system */
/* forward func declations */
static int sem_valid(sem_t * sem);
static void sem_release(sem_t * sem);
static void sem_ref(sem_t * sem);
static int sem_copyin(sem_t ** usp, sem_t ** ksp);


int sem_init(sem_t ** sp, uint32_t val)
{
    task_t *task = task_self();
    sem_t *s = NULL;

    if (task->nobjects > MAXOBJ_NUM)
	return -1;

    if (copyin(sp, &s, sizeof(sp)))
	return -1;

    sched_lock();
    /* checking is the sem exists */
    if (sem_valid(s)) {
	if (s->owner != task) {
	    sched_unlock();
	    return -4;
	}
	if (event_waiting(&s->evt)) {	/* found waiting threads */
	    sched_unlock();
	    return -2;
	}
	/* resetting sem value */
	s->value = val;
    } else {			/* create new sem */
	if ((s = (sem_t *) kmalloc(sizeof(sem_t), GFP_KERNEL)) == NULL) {
	    sched_unlock();
	    return -5;
	}
	if (copyout(&s, sp, sizeof(s))) {
	    kfree(s);
	    sched_unlock();
	    return -1;
	}

	event_init(&s->evt, "sem");
	s->owner = task;
	s->ref_cnt = 1;
	s->value = val;
	/* adding into lists */
	task->nobjects++;	/* inc the num of sync objects in task */
	s->next = sem_list;
	sem_list = s;
	list_insert(&task->sems, &s->task_link);
    }
    sched_unlock();

    return 0;
}

int sem_destroy(sem_t ** sp)
{
    task_t *task = task_self();
    sem_t *s = NULL;

    sched_lock();
    if (sem_copyin(sp, &s) || s->owner != curtask) {
	sched_unlock();
	return -1;
    }
    /* checking the owner of sem */
    if (s->owner != task) {
	sched_unlock();
	return -2;
    }
    if (!queue_empty(&s->evt.waitq)) {	/* found waiting threads */
	sched_unlock();
	return -3;
    } else {
	sem_release(s);
    }
    sched_unlock();

    return 0;
}

/* lock a sem */
int sem_wait(sem_t ** sp, uint32_t timeout)
{
    sem_t *s = NULL;
    int rc, error = 0;

    sched_lock();
    if (sem_copyin(sp, &s)) {
	sched_unlock();
	return -1;
    }
    sem_ref(s);

    while (s->value == 0) {
	rc = sched_thsleep(&s->evt, timeout);
	if (rc == SLEEP_TIMEOUT) {
	    error = -2;
	    break;
	}
	if (rc == SLEEP_IRQ) {
	    error = -3;
	    break;
	}
	/*
	 * We have to check the semaphore value again
	 * because another thread may run and acquire
	 * the semaphore before us.
	 */
    };

    if (!error)
	s->value--;

    sem_release(s);
    sched_unlock();

    return (error);
}

/*
 * try to lock a semaphore.
 * if the semaphore is already locked, it just returns EAGAIN.
 */
int sem_trywait(sem_t ** sp)
{
    sem_t *s = NULL;

    sched_lock();
    if (sem_copyin(sp, &s)) {
	sched_unlock();
	return -1;
    }

    if (s->value == 0) {
	sched_unlock();
	return -2;		/* try again */
    }

    s->value--;
    sched_unlock();

    return 0;
}

/* unlock a semaphore */
int sem_post(sem_t ** sp)
{
    sem_t *s = NULL;

    sched_lock();
    if (sem_copyin(sp, &s)) {
	sched_unlock();
	return -1;
    }

    s->value++;
    if (s->value > 0)
	sched_wakeupone(&s->evt);

    sched_unlock();

    return 0;
}

void sem_cleanup(task_t * task)
{
    list_t *head, *n;
    sem_t *s = NULL;

    head = &task->sems;
    for (n = list_first(head); n != head; n = list_next(n)) {
	s = list_entry(n, sem_t, task_link);
	sem_release(s);
    }
}

int sem_getval(sem_t ** sp, uint32_t * val)
{
    sem_t *s = NULL;

    sched_lock();
    if (sem_copyin(sp, &s)) {
	sched_unlock();
	return -1;
    }
    if (copyout(&s->value, val, sizeof(s->value))) {
	sched_unlock();
	return -2;
    }
    sched_unlock();

    return 0;
}

static int sem_valid(sem_t * sem)
{
    sem_t *s = NULL;

    if (!sem)
	return 0;

    sched_lock();
    s = sem_list;
    do {
	if ((s == sem) && (s->owner == sem->owner)) {
	    sched_unlock();
	    return 1;
	}
	s = s->next;
    } while (s != NULL);
    sched_unlock();
    /* not found sem! */
    return 0;
}

static void sem_release(sem_t * sem)
{
    sem_t *s;

    if (--s->ref_cnt > 0)
	return;

    sched_lock();
    list_remove(&s->task_link);
    s->owner->nobjects--;

    s = sem_list;
    while (s != NULL) {
	if (s == sem) {
	    break;		/* found sem */
	}
	s = s->next;
    };

    kfree(sem);
    sched_unlock();
}

static void sem_ref(sem_t * sem)
{
    if (!sem)
	return;

    sem->ref_cnt++;
}

static int sem_copyin(sem_t ** usp, sem_t ** ksp)
{
    sem_t *s = NULL;

    if (copyin(usp, &s, sizeof(usp)) || !sem_valid(s))
	return -1;

    *ksp = s;
    return 0;
}
